/**
 * This file is part of 1genia trampoline
 * Copyright (C) 2007-2008 1genia (contact@1genia.com)
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; version 3 of the License. 
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details. 
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; see the file COPYING.TXT.  If not,
 * write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301, USA. 
 **/
package com.genia.toolbox.projects.csv.business.manager.impl;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;

import com.genia.toolbox.basics.exception.BundledException;
import com.genia.toolbox.persistence.exception.PersistenceException;
import com.genia.toolbox.projects.csv.bean.Binder;
import com.genia.toolbox.projects.csv.bean.EntityBinder;
import com.genia.toolbox.projects.csv.bean.FieldBinder;
import com.genia.toolbox.projects.csv.business.exception.MappingException;
import com.genia.toolbox.projects.csv.business.manager.FillNewEntityBinderVisitor;
import com.genia.toolbox.projects.toolbox_basics_project.bean.model.I18nDynamicString;
import com.genia.toolbox.projects.toolbox_basics_project.bean.model.I18nDynamicValue;
import com.genia.toolbox.projects.toolbox_basics_project.bean.model.impl.I18nDynamicStringImpl;
import com.genia.toolbox.projects.toolbox_basics_project.bean.model.impl.I18nDynamicTextImpl;
import com.genia.toolbox.projects.toolbox_basics_project.spring.manager.I18nDynamicEntityManager;

/**
 * implementation of {@link FillNewEntityBinderVisitor}.
 */
public class FillNewEntityBinderVisitorImpl
    implements FillNewEntityBinderVisitor
{
  /**
   * error when the unable to bind an object to the entity.
   */
  public static final String KEY_UNABLE_TO_BIND_OBJECT_ERROR = FillNewEntityBinderVisitorImpl.class.getName() + ".error.unableToBindObject";

  /**
   * error whene unable to find the method needed to bind an object to an
   * entity.
   */
  public static final String KEY_UNABLE_TO_FIND_METHOD_TO_BIND_OBJECT_ERROR = FillNewEntityBinderVisitorImpl.class.getName() + ".error.unableToFindMethodToBindObject";

  /**
   * the current values of the entities.
   */
  private final ThreadLocal<Map<String, Object>> entitiesValues = new ThreadLocal<Map<String, Object>>();

  /**
   * the criteria to fill.
   */
  private final ThreadLocal<Object> entity = new ThreadLocal<Object>();

  /**
   * the values of the field of the csv.
   */
  private final ThreadLocal<Map<String, Object>> fieldsValues = new ThreadLocal<Map<String, Object>>();

  /**
   * the {@link I18nDynamicEntityManager} to use.
   */
  private I18nDynamicEntityManager i18nDynamicEntityManager;



  /**
   * clear the setted values to prevent memory leak.
   */
  public void clear()
  {
    entitiesValues.remove();
    fieldsValues.remove();
    entity.remove();
  }



  /**
   * getter for the i18nDynamicEntityManager property.
   * 
   * @return the i18nDynamicEntityManager
   */
  public I18nDynamicEntityManager getI18nDynamicEntityManager()
  {
    return i18nDynamicEntityManager;
  }



  /**
   * setter for the entitiesValues property.
   * 
   * @param entitiesValues
   *          the entitiesValues to set
   */
  public void setEntitiesValues(Map<String, Object> entitiesValues)
  {
    this.entitiesValues.set(entitiesValues);
  }



  /**
   * setter for the entity property.
   * 
   * @param entity
   *          the entity to set
   */
  public void setEntity(Object entity)
  {
    this.entity.set(entity);
  }



  /**
   * setter for the fieldsValues property.
   * 
   * @param fieldsValues
   *          the fieldsValues to set
   */
  public void setFieldsValues(Map<String, Object> fieldsValues)
  {
    this.fieldsValues.set(fieldsValues);
  }



  /**
   * setter for the i18nDynamicEntityManager property.
   * 
   * @param dynamicEntityManager
   *          the i18nDynamicEntityManager to set
   */
  public void setI18nDynamicEntityManager(I18nDynamicEntityManager dynamicEntityManager)
  {
    i18nDynamicEntityManager = dynamicEntityManager;
  }



  /**
   * the actual visit method.
   * 
   * @param binder
   *          the binder to visit
   * @param bindedObjectValue
   *          the value of the object to bind with
   * @throws PersistenceException
   *           if an error occured
   * @throws MappingException
   *           if an error occured
   */
  @SuppressWarnings("unchecked")
  public void visitBinder(Binder binder, Object bindedObjectValue)
      throws PersistenceException, MappingException
  {
    Class<?> bindedObjectClass = binder.getBindedObjectClass();

    Method methodToCall = null;
    if (Map.class.isAssignableFrom(bindedObjectClass) || Collection.class.isAssignableFrom(bindedObjectClass)) {
      methodToCall = getMethod("add", binder);
    }
    else {
      methodToCall = getMethod("set", binder);
      if (I18nDynamicValue.class.isAssignableFrom(bindedObjectClass)) {
        I18nDynamicValue i18nDynamicValue = null;
        if (I18nDynamicString.class.isAssignableFrom(bindedObjectClass)) {
          i18nDynamicValue = new I18nDynamicStringImpl();
        }
        else {
          i18nDynamicValue = new I18nDynamicTextImpl();
        }
        Map<Locale, String> values = (Map<Locale, String>) bindedObjectValue;
        for (Entry<Locale, String> entry : values.entrySet()) {
          getI18nDynamicEntityManager().setValue(i18nDynamicValue, entry.getKey(), entry.getValue());
        }
        bindedObjectValue = i18nDynamicValue;
      }
    }
    try {
      methodToCall.invoke(entity.get(), bindedObjectValue);
    }
    catch (Exception e) {
      throw new MappingException(e, KEY_UNABLE_TO_BIND_OBJECT_ERROR, binder.getEntityDescriptor().getFolderDescriptor().getFolderName(), binder.getEntityDescriptor().getName(), methodToCall.getName());
    }
  }



  /**
   * visit method for {@link EntityBinder}.
   * 
   * @param binder
   *          the binder to visit
   * @throws BundledException
   *           if an error occured
   * @see com.genia.toolbox.projects.csv.bean.BinderVisitor#visitEntityBinder(EntityBinder)
   */
  public void visitEntityBinder(EntityBinder binder)
      throws BundledException
  {
    visitBinder(binder, entitiesValues.get().get(binder.getEntityName()));
  }



  /**
   * visit method for {@link FieldBinder}.
   * 
   * @param binder
   *          the binder to visit
   * @throws BundledException
   *           if an error occured
   * @see com.genia.toolbox.projects.csv.bean.BinderVisitor#visitFieldBinder(FieldBinder)
   */
  public void visitFieldBinder(FieldBinder binder)
      throws BundledException
  {
    visitBinder(binder, fieldsValues.get().get(binder.getFieldName()));
  }



  /**
   * retrieve a method given the name of the property to modify and the kind of
   * modifier (setter or adder).
   * 
   * @param methodPrefix
   *          the kind of modifier
   * @param binder
   *          the binder describing the property
   * @return a method given the name of the property to modify and the kind of
   *         modifier (setter or adder)
   * @throws MappingException
   *           if an error occured
   */
  private Method getMethod(String methodPrefix, Binder binder)
      throws MappingException
  {
    String propertyName = binder.getPropertyName();
    String methodName = methodPrefix + Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1);

    for (Method method : entity.get().getClass().getMethods()) {
      if (Modifier.isPublic(method.getModifiers()) && !Modifier.isStatic(method.getModifiers()) && method.getName().equals(methodName)) {
        return method;
      }
    }

    throw new MappingException("Unable to find method " + methodName + " of class " + entity.get().getClass(), KEY_UNABLE_TO_FIND_METHOD_TO_BIND_OBJECT_ERROR, binder.getEntityDescriptor()
        .getFolderDescriptor().getFolderName(), binder.getEntityDescriptor().getName(), methodName);
  }

}